
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <list>
#include "xplugin_rtc_debug.h"
#include "xtlv_file.h"
#include "xm_log.h"
#include "xcutil.h"
#include "xrtc_endpoint.h"
#include "audio_recveq.h"
#include "xmixer.h"
#include "xrtc_endpoint_connector.h"

#define debug_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, "debug", ##ARGS )
#define debug_dbge(FMT, ARGS...) dbge("|%s| " FMT, "debug", ##ARGS )

#define ZERO_ALLOC(o, type, sz) do{o=(type)malloc(sz); memset(o, 0, sz);}while(0)



typedef enum{
    DM_LOOPBACK_ALL = 0,
    DM_LOOPBACK_AUDIO,
    DM_MIXER_PCM,
    DM_CONNECTOR
}debug_mode_t;



typedef struct rtc_debug_st * rtc_debug_t;

struct rtc_debug_st{
    debug_mode_t mode;
    rtc_endpoint_t rtc;
    int is_loopback;
    tlv_file_t tlv;
    rtc_connector_t connector;
    
};



static char const * g_app_name = NULL;
// static xmixer_t g_mixer = NULL;
// static guint g_mixer_timer_id = 0;
//static rtc_connector_t g_connector = NULL;
static rtc_endpoint_t g_pub_rtc1 = NULL;
static rtc_endpoint_t g_pub_rtc2 = NULL;
//static debug_mode_t g_debug_mode = DM_LOOPBACK_ALL;
//static debug_mode_t g_debug_mode = DM_LOOPBACK_AUDIO;
//static debug_mode_t g_debug_mode = DM_MIXER_PCM;
static debug_mode_t g_debug_mode = DM_CONNECTOR;

static void xplugin_rtc_debug_delete(rtc_debug_t obj);

static
void on_debug_pub_rtc_destroy(rtc_endpoint_t rtc, void * context){
    if(rtc ==  g_pub_rtc1){
        debug_dbgi("free rtc1");
        g_pub_rtc1 = NULL;
    }else if(rtc == g_pub_rtc2){
        debug_dbgi("free rtc2");
        g_pub_rtc2 = NULL;
    }else{
        debug_dbge("unexpect debug pub rtc error !!!!!!");
        return ;
    }
}

static
rtc_endpoint_callbacks g_debug_pub_rtc_listener = {
    .context = NULL, // context
    .on_destroy = on_debug_pub_rtc_destroy,
    .on_stream_data = NULL, //  on_stream_data;
    .on_event = NULL
    
};

int connector_debug_rtc(GMainContext * main_context, rtc_endpoint_t rtc, int is_loopback, const char * filename){
    if(!g_pub_rtc1 || !g_pub_rtc2){
        if(!g_pub_rtc1) {
            g_pub_rtc1 = rtc;
        }else{
            g_pub_rtc2 = rtc;
        }
        rtc_endpoint_register_callback(rtc, NULL, &g_debug_pub_rtc_listener);
        if(g_pub_rtc1 && g_pub_rtc2){
            debug_dbgi("connect rtc1 and rtc2");
            rtc_endpoint_subscribe(g_pub_rtc1, g_pub_rtc2, true, true);
            rtc_endpoint_subscribe(g_pub_rtc2, g_pub_rtc1, true, true);
        }
    }else{
        debug_dbgi("connect to rtc1");
        rtc_endpoint_subscribe(rtc, g_pub_rtc1, true, true);
    }
    
    return 0;
}


//
//
//static
//gboolean on_mix_timer_callback (gpointer user_data){
//    if(g_mixer){
//        xmixer_maybe_mix_frame(g_mixer);
//    }
//    return TRUE;
//}
//
//
//int xmixer_debug_rtc(GMainContext * main_context, rtc_endpoint_t rtc, int is_loopback, const char * filename){
//    if(!g_mixer){
//        g_mixer = xmixer_create("debug", is_loopback);
//        // todo: use filename to dump new tlv
//        //xmixer_set_filename(g_mixer, filename);
//        if(g_mixer_timer_id == 0){
//            g_mixer_timer_id = g_timeout_add (MIX_TIMER_INTERVAL, // milliseconds
//                                        on_mix_timer_callback,
//                                        g_mixer);
//        }
//    }
//    xmixer_add_rtc(g_mixer, rtc);
//    return 0;
//}


static
int loopback_all(rtc_endpoint_t rtc, void * context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){
    rtc_debug_t obj = (rtc_debug_t) context;
    if(data_type == RTC_RECV_CLEAR){
        if(obj->tlv){
            int64_t ts = get_timestamp_ms();
            tlv_file_write2(obj->tlv, stream_type, sizeof(ts), &ts, len, buf);
        }
        
        if(obj->is_loopback){
            rtc_endpoint_send_stream_data(rtc, stream_type, codec, fec_codec, is_rtcp, is_rtx, buf, len);
        }
        return true;
    }
    return false;
}

static
int loopback_audio_only(rtc_endpoint_t rtc, void * context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){
    rtc_debug_t obj = (rtc_debug_t) context;
    if(data_type == RTC_RECV_CLEAR){
        if(obj->is_loopback){
            if(stream_type == RTC_AUDIO && !is_rtcp){
                rtc_endpoint_send_stream_data(rtc, stream_type, codec, fec_codec, is_rtcp, is_rtx, buf, len);
                return true;
            }
        }
    }
    return false;
}


static
bool on_rtc_stream_data(rtc_endpoint_t rtc, void * context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){
//    loopback_all
//    loopback_audio_only
    rtc_debug_t obj = (rtc_debug_t) context;
    if(obj->mode == DM_LOOPBACK_ALL){
        return loopback_all(rtc, context, stream_type, codec, fec_codec, data_type, is_rtcp, is_rtx, buf, len);
    }else if(obj->mode == DM_LOOPBACK_AUDIO){
        return loopback_audio_only(rtc, context, stream_type, codec, fec_codec, data_type, is_rtcp, is_rtx, buf, len);
    }

    return false;
}

static
void on_rtc_stream_event(rtc_endpoint_t rtc, void * context, int event, void * event_arg1, int event_arg2){
    if(event == RTC_EVENT_SETUP){
        debug_dbgi("rtc [%s] setup", rtc_endpoint_get_id(rtc));
    }
}


static
void on_rtc_destroy(rtc_endpoint_t rtc, void * context){
    debug_dbgi("on_rtc_destroy");
    rtc_debug_t obj = (rtc_debug_t) context;
    xplugin_rtc_debug_delete(obj);
    debug_dbgi("on_rtc_destroy end");
}



static
rtc_endpoint_callbacks g_rtc_listener = {
    .context = NULL, // context
    .on_destroy = on_rtc_destroy,
    .on_stream_data = on_rtc_stream_data, //  on_stream_data;
    .on_event = on_rtc_stream_event
    
};


static
void xplugin_rtc_debug_delete(rtc_debug_t obj){
    if(!obj) return;

    
    if(obj->tlv){
        tlv_file_close(obj->tlv);
        obj->tlv = NULL;
    }

    free(obj);
}



static
const char * get_debug_file_name(){
//////  case1: filename with date
    
//    static char filename[1024];
//    time_t nowtime;
//    struct tm *timeinfo;
//    nowtime = time(NULL);
//    timeinfo = localtime(&nowtime);
//    
//    //        sprintf(filename, "%s.rtc.%04d%02d%02d-%02d%02d%02d.%s.tlv"
//    //                , g_app_name
//    //                , timeinfo->tm_year+1900
//    //                , timeinfo->tm_mon+1
//    //                , timeinfo->tm_mday
//    //                , timeinfo->tm_hour
//    //                , timeinfo->tm_min
//    //                , timeinfo->tm_sec
//    //                , rtc_endpoint_get_id(rtc)
//    //                );
//    sprintf(filename, "%s.rtc.latest.tlv", g_app_name);
//    return filename;
    
    
    
    
//////  case2: filename with fixing name
    
//    static char filename[1024];
//    sprintf(filename, "%s.rtc.latest.pcm", g_app_name);
//    return filename;
    
    return NULL;
}

int xplugin_rtc_debug_rtc(GMainContext * main_context, rtc_endpoint_t rtc, const char* debug_mode){
    int ret = -1;
    rtc_debug_t obj = NULL;

    if (strcmp(debug_mode, "mixer") == 0) {
        g_debug_mode = DM_MIXER_PCM;
    } else if (strcmp(debug_mode, "connector") == 0) {
        g_debug_mode = DM_CONNECTOR;
    } else if (strcmp(debug_mode, "loopback-audio") == 0) {
        g_debug_mode = DM_LOOPBACK_AUDIO;
    } else if (strcmp(debug_mode, "loopback") == 0) {
        g_debug_mode = DM_LOOPBACK_ALL;
    } else {
        return 0;
    }

//    if(g_debug_mode == DM_MIXER_PCM){
//        int is_loopback = 0;
//        xmixer_debug_rtc(main_context, rtc, is_loopback, get_debug_file_name());
//        return 0;
//    }else
    if(g_debug_mode == DM_CONNECTOR){
        connector_debug_rtc(main_context, rtc, 0, get_debug_file_name());
        return 0;
    }
    
    do {
        ZERO_ALLOC(obj, rtc_debug_t, sizeof(struct rtc_debug_st));
        obj->rtc = rtc;
        obj->is_loopback = 1;
        obj->mode = g_debug_mode;

        const char * filename = get_debug_file_name();
        if(filename){
            obj->tlv = tlv_file_open(filename);
            if(!obj->tlv){
                debug_dbge("fail to write-open %s", filename);
                ret = -1;
                break;
            }
            
//            obj->fpout = fopen(filename, "wb");
            
        }
        
        rtc_endpoint_register_callback(rtc, obj, &g_rtc_listener);
        ret = 0;
    } while (0);
    
    if(ret){
        xplugin_rtc_debug_delete(obj);
    }

    return 0;
}

int xplugin_rtc_debug_init(int argc, char **argv){
    g_app_name = argv[0];
    return 0;
}

void xplugin_rtc_debug_exit(){
    
}





typedef struct rtc_switcher_st * rtc_switcher_t;
typedef struct switch_conference_st * switch_conference_t;

typedef std::map<std::string, switch_conference_t> confr_map_d;
typedef std::map<std::string, rtc_endpoint_t> rtc_map_d;

struct switch_conference_st{
    rtc_map_d * rtc_map;
    rtc_endpoint_t rtc1;
    rtc_endpoint_t rtc2;
};

struct rtc_switcher_st{
    confr_map_d * confr_map;
};

void xplugin_rtc_switcher_on_destroy(void * context){
    debug_dbgi("xplugin_rtc_switcher_on_destroy");
    rtc_switcher_t obj = (rtc_switcher_t)context;
    if(obj->confr_map){
        delete obj->confr_map;
        obj->confr_map = NULL;
    }
    free(obj);
    debug_dbgi("xplugin_rtc_switcher_on_destroy end");
}

static
bool on_switcher_rtc_stream_data(rtc_endpoint_t rtc, void * context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){
    switch_conference_t confr = (switch_conference_t) context;
    if(data_type == RTC_RECV_CLEAR ){
        if(rtc == confr->rtc1){
            if(confr->rtc2){
                rtc_endpoint_send_stream_data(confr->rtc2, stream_type, codec, fec_codec, is_rtcp, is_rtx, buf, len);
            }
            
        }else if(rtc == confr->rtc2){
            if(confr->rtc1){
                rtc_endpoint_send_stream_data(confr->rtc1, stream_type, codec, fec_codec, is_rtcp, is_rtx, buf, len);
            }
            
        }else{
            debug_dbge("data from unknown rtc %s", rtc_endpoint_get_id(rtc));
        }
        
    }
    return false;
}

static
void on_switcher_rtc_destroy(rtc_endpoint_t rtc, void * context){
    debug_dbgi("on_switcher_rtc_destroy");
    switch_conference_t confr = (switch_conference_t) context;
    if(rtc == confr->rtc1){
        confr->rtc1 = NULL;
    }else if(rtc == confr->rtc2){
        confr->rtc2 = NULL;
    }else{
        debug_dbge("destroy for unknown rtc %s", rtc_endpoint_get_id(rtc));
    }
    debug_dbgi("on_switcher_rtc_destroy end");
}

static
rtc_endpoint_callbacks g_switcher_rtc_listener = {
    .context = NULL,
    .on_destroy = on_switcher_rtc_destroy,
    .on_stream_data = on_switcher_rtc_stream_data,
    .on_event = NULL
    
};

void xplugin_rtc_switcher_on_new_rtc(void * context, rtc_endpoint_t rtc){
    rtc_switcher_t obj = (rtc_switcher_t)context;
    const char * confr_id = "test_confr";
    switch_conference_t confr = NULL;
    confr_map_d::iterator confr_it = obj->confr_map->find(confr_id);
    if (confr_it == obj->confr_map->end()){
        debug_dbgi("NOT found confr_id %s, create it", confr_id);
        ZERO_ALLOC(confr, switch_conference_t, sizeof(struct switch_conference_st));
        confr->rtc_map = new rtc_map_d;
        (*obj->confr_map)[confr_id] = confr;
    }else{
        confr = confr_it->second;
    }
    
    const char * peer_name = rtc_endpoint_get_id(rtc);
    
    rtc_map_d::iterator rtc_it = confr->rtc_map->find(peer_name);
    if(rtc_it != confr->rtc_map->end()){
        debug_dbgi("new rtc but exist, remove peer_name=%s", peer_name);
        confr->rtc_map->erase(rtc_it);
        if(rtc_it->second == confr->rtc1){
            confr->rtc1 = NULL;
        }else if(rtc_it->second == confr->rtc2){
            confr->rtc2 = NULL;
        }
    }
    if(!confr->rtc1){
        confr->rtc1 = rtc;
    }else if(!confr->rtc2){
        confr->rtc2 = rtc;
    }else{
        debug_dbge("too much rtc");
        return;
    }
    (*confr->rtc_map)[peer_name] = rtc;
    rtc_endpoint_register_callback(rtc, confr, &g_switcher_rtc_listener);
    
}

static
xplugin_rtc_ops g_switcher_ops = {
    .context = NULL,
    .on_destroy = xplugin_rtc_switcher_on_destroy,
    .on_new_rtc = xplugin_rtc_switcher_on_new_rtc,
    
};

xplugin_rtc_ops * xplugin_rtc_switcher_instance(){
    rtc_switcher_t obj = NULL;
    ZERO_ALLOC(obj, rtc_switcher_t, sizeof(struct rtc_switcher_st));
    obj->confr_map = new confr_map_d;
    g_switcher_ops.context = obj;
    return &g_switcher_ops;
}








typedef struct auto_record_st * auto_record_t;
struct auto_record_st{
    rtc_endpoint_t rtc;
    
    char pic_file_path[1024];
    tlv_file_t pic_tlv;
    int pic_done;
    
    char audio_file_path[1024];
    tlv_file_t audio_tlv;
    int audio_done;
    
    int64_t audio_start_time;
    int64_t pic_start_time;
};

static inline
void close_tlv_(tlv_file_t *ptlv, int *pdone, const char * filename){
    if(*ptlv){
        rtc_endpoint_write_tlv_tail(*ptlv);
        tlv_file_close(*ptlv);
        *ptlv = 0;
        *pdone = 1;
        
        char new_path[512];
        snprintf(new_path, sizeof(new_path), "%s.tlv", filename);
        int error = rename(filename, new_path);
        debug_dbgi("rename err=%d, [%s] -> [%s]", error, filename, new_path);
    }
}

static inline
void auto_record_rtc_delete(auto_record_t obj){
    if(!obj) return;
    
    close_tlv_(&obj->audio_tlv, &obj->audio_done, obj->audio_file_path);
    close_tlv_(&obj->pic_tlv,  &obj->pic_done, obj->pic_file_path);

    free(obj);
}

static inline
int open_tlv_(rtc_endpoint_t rtc, tlv_file_t * ptlv, const char * filename){
    int ret = 0;
    do {
        if(!(*ptlv)){
            *ptlv = tlv_file_open(filename);
            if(!(*ptlv)){
                debug_dbge("fail to write-open tlv [%s]", filename);
                ret = -1;
                break;
            }
            debug_dbgi("successfully open tlv [%s]", filename);
            
            ret = rtc_endpoint_write_tlv_header(rtc, *ptlv);
            if(ret){
                break;
            }
        }
    } while (0);
    return ret;
}

static inline
void check_tlv(auto_record_t obj){
    do{
        if(!rtc_endpoint_is_setup(obj->rtc)) {
            break;
        }
        
        if(!obj->audio_tlv && !obj->audio_done){
            open_tlv_(obj->rtc, &obj->audio_tlv, obj->audio_file_path);
            obj->audio_start_time = get_timestamp_ms();
            rtc_endpoint_req_pli(obj->rtc);
        }
        
        if(!obj->pic_tlv && !obj->pic_done && obj->audio_start_time > 0){
            int64_t elapsed = get_timestamp_ms() - obj->audio_start_time;
            if(elapsed >= 10*1000 ){
                open_tlv_(obj->rtc, &obj->pic_tlv, obj->pic_file_path);
                rtc_endpoint_req_pli(obj->rtc);
                obj->pic_start_time = get_timestamp_ms();
            }
        }
        
        if(obj->audio_tlv && !obj->audio_done){
            int64_t elapsed = get_timestamp_ms() - obj->audio_start_time;
            if(elapsed >= 30*1000 ){
                close_tlv_(&obj->audio_tlv, &obj->audio_done, obj->audio_file_path);
            }
        }
        
        if(obj->pic_tlv && !obj->pic_done){
            int64_t elapsed = get_timestamp_ms() - obj->pic_start_time;
            if(elapsed >= 5*1000 ){
                close_tlv_(&obj->pic_tlv,  &obj->pic_done, obj->pic_file_path);
            }
        }
        
    }while(0);
    
}


static
bool auto_record_on_rtc_stream_data(rtc_endpoint_t rtc, void * context, int stream_type, rtc_codec_id_t codec, rtc_codec_id_t fec_codec, int data_type, int is_rtcp, int is_rtx, unsigned char * buf, int len){

    auto_record_t obj = (auto_record_t) context;
    if(data_type == RTC_RECV_CLEAR){
        if(obj->audio_tlv){
            rtc_endpoint_write_tlv_rtp_rtcp(obj->audio_tlv, is_rtcp, buf, len);
        }
        if(obj->pic_tlv){
            rtc_endpoint_write_tlv_rtp_rtcp(obj->pic_tlv, is_rtcp, buf, len);
        }
    }

    return false;
}

static
void auto_record_on_rtc_stream_event(rtc_endpoint_t rtc, void * context, int event, void * event_arg1, int event_arg2){
    auto_record_t obj = (auto_record_t) context;
    if(event == RTC_EVENT_SETUP){
        debug_dbgi("rtc [%s] setup", rtc_endpoint_get_id(rtc));
        check_tlv(obj);
    }else if(event == RTC_EVENT_TIMER){
        check_tlv(obj);
    }
}


static
void auto_record_on_rtc_destroy(rtc_endpoint_t rtc, void * context){
    debug_dbgi("auto_record_on_rtc_destroy");
    auto_record_t obj = (auto_record_t) context;
    auto_record_rtc_delete(obj);
    debug_dbgi("auto_record_on_rtc_destroy end");
}



static
rtc_endpoint_callbacks g_auto_record_rtc_listener = {
    .context = NULL, // context
    .on_destroy = auto_record_on_rtc_destroy,
    .on_stream_data = auto_record_on_rtc_stream_data,
    .on_event = auto_record_on_rtc_stream_event
    
};


#include <sys/stat.h>
static inline
int mk_dir_tree(const char * path){
    struct stat st = {0};
    
    if (stat(path, &st) == -1) {
        return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
    }
    return 0;
}

int auto_record_rtc(rtc_endpoint_t rtc, const char * record_path){ 
    auto_record_t obj = NULL;
    int ret = 0;
    
    do{
        ZERO_ALLOC(obj, auto_record_t, sizeof(auto_record_st));
        obj->rtc = rtc;
        
        ret = mk_dir_tree(record_path);
        if(ret){
            debug_dbgi("fail to mkdir [%s]", record_path);
            break;
        }
        
        snprintf(obj->pic_file_path, sizeof(obj->pic_file_path),     "%s/%s-extract-picture", record_path, rtc_endpoint_get_id(rtc));
        snprintf(obj->audio_file_path, sizeof(obj->audio_file_path), "%s/%s-extract-wave", record_path, rtc_endpoint_get_id(rtc));
        
        check_tlv(obj);
        
        rtc_endpoint_register_callback(rtc, obj, &g_auto_record_rtc_listener);
        
        ret = 0;
    }while(0);
    
    if(ret){
        auto_record_rtc_delete(obj);
    }
    
    return ret;
}



